3. Improving Programs Using Methods
The Great Programmer has
just been around and has taken a look at your code. She purses her lips
when she sees something she doesn’t like, and she’s doing that now. The
bit of code she doesn’t like is the scaleSprites method where you set up the bread and cheese sprites:
void scaleSprites()
{
cheese.TicksToCrossScreen = 200.0f;
cheese.WidthFactor = 0.05f;
cheese.SpriteRectangle.Width =
(int)((displayWidth * cheese.WidthFactor) + 0.5f);
float aspectRatio =
(float)cheese.SpriteTexture.Width / cheese.SpriteTexture.Height;
cheese.SpriteRectangle.Height =
(int)((cheese.SpriteRectangle.Width / aspectRatio) + 0.5f);
cheese.X = minDisplayX;
cheese.Y = minDisplayY;
cheese.XSpeed = displayWidth / cheese.TicksToCrossScreen;
cheese.YSpeed = cheese.XSpeed;
bread.WidthFactor = 0.15f;
bread.TicksToCrossScreen = 120.0f;
bread.SpriteRectangle.Width =
(int)((displayWidth * bread.WidthFactor) + 0.5f);
aspectRatio =
(float)bread.SpriteTexture.Width / bread.SpriteTexture.Height;
bread.SpriteRectangle.Height =
(int)((bread.SpriteRectangle.Width / aspectRatio) + 0.5f);
bread.X = displayWidth / 2;
bread.Y = displayHeight / 2;
bread.XSpeed = displayWidth / bread.TicksToCrossScreen;
bread.YSpeed = bread.XSpeed;
}
For
a start, she reckons that the name is no longer correct. The method
doesn’t only scale the sprites; it also sets their initial position on
the screen and their speed of movement. So you promise to go through and
change the name of the method.
The next thing she doesn’t like to see is the same piece of code
repeated. Rather than perform exactly the same sequence of statements
for the bread as for the cheese, she suggests that you make a method
called setupSprite that sets up a
sprite. You then call this for every sprite you want to set up. You know
that you’ll have tomato sprites later, so this seems like a sensible,
time-saving plan. You can pass the setupSprite method parameters that give it all the information it needs to work on, so you begin to write the method:
void setupSprite(
GameSpriteStruct sprite,
float widthFactor,
float ticksToCrossScreen,
float initialX,
float initialY)
{
sprite.WidthFactor = widthFactor;
sprite.TicksToCrossScreen = ticksToCrossScreen;
sprite.SpriteRectangle.Width = (int)((displayWidth * widthFactor) + 0.5f);
float aspectRatio =
(float)sprite.SpriteTexture.Width / sprite.SpriteTexture.Height;
sprite.SpriteRectangle.Height =
(int)((sprite.SpriteRectangle.Width / aspectRatio) + 0.5f);
sprite.X = initialX;
sprite.Y = initialY;
sprite.XSpeed = displayWidth / ticksToCrossScreen;
sprite.YSpeed = sprite.XSpeed;
}
The method is given the
sprite to set up, along with the width factor, the time taken to cross
the screen, and the initial start position of the sprite. You can then
set up the cheese and bread by making two calls of the method:
void setupSprites()
{
setupSprite(cheese, 0.05f, 200.0f, minDisplayX, minDisplayY);
setupSprite(bread, 0.15f, 120.0f, displayWidth / 2, displayHeight / 2);
}
This looks much neater,
and you’re really pleased with the code that you’ve written. You feed
all your setup values into the method call, and it calculates the
content of the gameSpriteStruct that needs
to be set up. The only problem is that it doesn’t work. The method call
doesn’t seem to have any effect on the bread or cheese sprite value.
3.1. Value and Reference Parameters
It turns out that your program doesn’t work because the parameters in your method are passed by value. A parameter is the means by which you can pass information into a method. When
a method is called, the value given in the call is copied into the
parameter. This means that when code in a method assigns a value to the
parameter, the copy is changed, but not the original. In other words,
the statement sprite.X = initialX; changes the value of a copy of the GameSpriteStruct that was supplied as a parameter. When a method ends, all the parameter copies are discarded, and the updated values are lost.
Passing value
parameters into method calls is fine when you want to tell a method
something, but it is less useful when you want the method to change the
parameter. To make the method useful, you need to find a way of pointing
the method at the variable you want it to change. It turns out that you
have a way to do this, and you’ve seen it before. The device you’ll use
is called a reference.
If you give the method a reference to the thing you want it to change,
it can follow the reference and make changes to your actual bread and
cheese objects rather than to copies. C# structures are managed by value, which is why the values
of the cheese and bread sprites get copied when the method is called. To
tell C# to manage a particular parameter as a reference, you need to
change the header of the method:
void setupSprite(
ref GameSpriteStruct sprite,
float widthFactor,
float ticksToCrossScreen,
float initialX,
float initialY)
{
// method goes here
}
The ref modifier before the GameSpriteStruct
parameter in the method header tells the compiler to pass a reference
to the parameter’s location in memory rather than copying a value stored
in that memory location. You also need to use the ref modifier, as shown here in bold, when you make a call to the method:
setupSprite(ref cheese, 0.05f, 200.0f,
minDisplayX, minDisplayY);
setupSprite(ref bread, 0.15f, 120.0f,
displayWidth/2, displayHeight/2);
Now, when setupSprite runs, it is given the values of the rest of the parameters that it needs to work with and a reference to the GameSpriteStruct
object that needs to be changed. You don’t need to change any code in
the body of the method itself; the compiler makes sure that the
instructions it produces follow the reference and update the correct
values in memory rather than updating a copy of the values.
4. Handling Collisions
You
have a bread bat and some cheese, and you can move the bread around the
game and chase the cheese, but nothing happens when you hit the cheese
with the bread. You now need to add the interaction between these two
sprites. The first thing the game needs to do is detect when the bread
and the cheese collide. The best way to do this is to use the rectangles
that define the size and position of the two sprites on the screen.
When these two rectangles intersect (that is, both of them cover the
same part of the screen), it means that a collision has taken place. Figure 2 shows how this works.
What you need is a method
that you can use to detect when this happens. Fortunately, the designers
of XNA have provided just such a method using the Rectangle type. The method, called Intersects, is used as follows:
if (cheese.SpriteRectangle.Intersects(bread.SpriteRectangle))
{
// we have a collision
}
You call the Intersects method on one rectangle and feed it the other one to compare with it. It returns true
if the two rectangles intersect. Note that in the previous code, it is
necessary to get the rectangle value of the bread and cheese sprites.